Terraformを使ってS3とAWS Batchを連携するようAWSリソース作成してみた
みなさん、こんにちは!
クルトンです。
今回は、S3バケットの特定のフォルダへファイルをアップロードをした時に、AWS Batchのコンテナを動かしてみます。AWSリソース作成にはTerraformを使っているので、関連するAWSサービスをTerraformで書きたい方には参考になるかと思います。
前提
処理する流れとしては、次のものを想定しています。
- S3
- コンテナ内で使用するファイルを指定フォルダへアップロード
- EventBridge
- アップロードを検知すると、AWS Batchを動かす
- AWS Batch
- S3へアップロードされたファイルを使用してコンテナ内で処理をし、アウトプットとしてインプットで使用したファイルと同じS3バケットへファイルアップロード
- S3
- AWS Batchのコンテナでのアウトプットされたファイルが指定されたフォルダへ格納される
コンテナ
コンテナは次の流れで処理するものを作成済みとします。
- S3からファイルをダウンロードする
- ダウンロードしたファイルをインプットとして使う機械学習モデルを GPU環境 で動かす
- 機械学習モデルのアウトプットしたファイルをS3へアップロード
※インプット・アウトプット両方のファイル形式はCSVファイルとする
「コンテナをどう作成したら良いのか?」については、次のブログをご参考にしてください。
AWS Batch上でGPUを使うコンテナの動かし方については、次のブログをご参考にしてください。
Terraformの環境構築について
リソース作成についてはTerraformを使用しています。Terraformを使ってのリソース作成をした事が無い方は、環境構築から始めてみてください。
参考になるのが次のブログです。
Mac環境お使いの方向け
Windows環境お使いの方向け
MFA設定しているIAMを使いたい方へ
Terraformにおいて、MFAを設定しているIAMを使いたい方は次のブログが参考になります。(リンク先はMac環境で設定していますがaws-vaultはWindowsでも使えます。)
概要
次のように、ファイルを3つ作成します。ファイル名については、他の名前を使っても良いです。
. ├── main.tf ├── secrets.tfvars └── variables.tf
main.tf
- 各AWSリソース作成について書かれている本体のファイル
variables.tf
- variableで変数定義をしているファイル
secrets.tfvars
- variables.tfファイルで定義している変数の値を定義しているファイル
以上3つのファイルを作成し、terraform init
実行後にterraform apply
コマンドを実行して、AWSリソースを作成します。
次以降の内容は、AWSリソースを書くのにどのようにtfファイルを書くと良いかを説明していきます。
説明の順番は次のとおりです。
- providerの定義
- 記述するファイル: main.tf
- 変数の定義
- 記述するファイル: main.tf, variables.tf, secrets.tfvars
- S3の設定
- 記述するファイル: main.tf
- AWS Batchcの設定
- 記述するファイル: main.tf
- EventBridgeの設定
- 記述するファイル: main.tf
Terraformの書き方が分からない場合は、公式サイトをご確認ください。
なお、本ブログで使う内容は以下のものですので、全て知っている方は次の内容にお読みいただければ幸いです。
providerの定義
main.tf
ファイルの冒頭に、以下内容を記載してください。
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 4.16" } } required_version = ">= 1.2.0" }
変数定義
ハードコーディングにならないよう主にlocalsブロックを使って、各AWSリソースを作成するときに使用する内容を変数で保持します。(一部variableブロックを使っています。)
main.tfファイル
locals { prefix = "お好きな名前をご記入ください" # 自分が作成したAWSリソースを分かりやすくするために先頭文字列として付ける # S3 bucket_name = "${local.prefix}-bucket" # AWS Batch job_name = "${local.prefix}-job" job_definition = "${local.prefix}-job-definition" compute_environment_name = "${local.prefix}-compute-environment" job_queue = "${local.prefix}-job-queue" S3_for_instance_role = "${local.prefix}-S3-for-instance-role" AmazonEC2ContainerServiceforEC2Role = "${local.prefix}-aws-batch-for-instance-role-AmazonEC2ContainerServiceforEC2Role" AWSBatchServiceRole = "${local.prefix}-AWSBatchServiceRole" aws_batch_service_role = "${local.prefix}-aws-batch-service_role" instance_role_profile = "${local.prefix}-terraform-iam-role-for-aws-batch-gpu" instance_role = "${local.prefix}-iam-role-for-aws-batch-gpu" # EventBridge event_rule = "${local.prefix}-event-rule" eventbridge_service_role = "${local.prefix}_eventbridge_service_role" eventbridge_policy = "${local.prefix}-eventbridge-policy" # VPC subnet_list = ["${var.subnet1}", "${var.subnet2}", "${var.subnet2}"] }
また変数とは別ですが、後で使う必要があるものについても一緒に定義してしまいます。
# 現在使用しているアカウントIDを自動取得 data "aws_caller_identity" "current" {} # 現在使用しているリージョンを自動取得 data "aws_region" "current" {}
variables.tfファイル
すでに作成されているデフォルトVPCを使っているため、そのVPCを使用するための変数宣言しております。 また、ECRにプッシュされているカスタムコンテナを使用するのに必要な変数を宣言しています。
ここでは変数名のみを定義しており、変数で保持する値については secrets.tfvars
ファイルで定義します。
# VPC variable "subnet1" {} variable "subnet2" {} variable "subnet3" {} variable "security_group_ids" {} # AWS Batch variable "container_name" {}
secrets.tfvars
variables.tf
ファイルで変数宣言したものについて、具体的な値を書きます。
# VPC subnet1 = "subnet-aaaaaaaa" subnet2 = "subnet-bbbbbbbb" subnet3 = "subnet-cccccccc" security_group_ids = "sg-xxxxxxxx" # AWS Batch container_name = "コンテナ名を記載"
以上で、3つのファイルにまたがって使用する変数について定義しました。それでは実際に宣言した変数を使いながらAWSリソースを定義してみましょう。
main.tfファイルでAWSリソースの定義
どのような内容を書くのかをAWSサービスごとに記載していきます。
S3の設定
一点注意が必要で、Bucketを作成するだけでなく、EventBridgeとの連携のための設定も必要です。
resource "aws_s3_bucket" "bucket" { bucket = local.bucket_name } resource "aws_s3_object" "input_folder" { bucket = aws_s3_bucket.bucket.id key = "input-file/" } # EventBridgeへ通知を送るのに必要 resource "aws_s3_bucket_notification" "bucket_notification" { bucket = aws_s3_bucket.bucket.bucket eventbridge = true }
AWS Batchの設定
Terraformで記載する内容は次のものです。対象のAWSサービスだけでなく、権限の設定としてIAM関連の設定も必要です。
- aws_batch_job_definition
- ジョブ定義の設定をします。
- aws_batch_compute_environment
- コンピューティング環境の設定をします。
- aws_batch_job_queue
- ジョブキューの設定をします。
- aws_iam_instance_profile
- コンテナを動かす際に必要となります。
- aws_iam_policy
- AWS定義済みのマネージドポリシーを使用するのに使っています。
- aws_iam_role_policy_attachment
- 権限を作成した時に、IAMロールへ付与するのに必要です。
- aws_iam_policy_document
- "batch.amazonaws.com"を書く必要があります。
- aws_iam_role
- インスタンスロールとサービスロールの2つを定義します。
# ジョブ定義 resource "aws_batch_job_definition" "job_definition" { name = local.job_definition type = "container" container_properties = <<CONTAINER_PROP { "command":["python","app.py","--input_bucket_name","Ref::input_bucket_name","--input_s3_key","Ref::input_s3_key","--output_bucket_name","Ref::output_bucket_name","--output_s3_key","Ref::output_s3_key"], "image":"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/${var.container_name}:latest", "resourceRequirements": [ { "value": "1", "type": "VCPU" }, { "value": "2048", "type": "MEMORY" }, { "value": "1", "type": "GPU" } ] } CONTAINER_PROP } # コンピューティング環境 resource "aws_batch_compute_environment" "compute_environment" { compute_environment_name = local.compute_environment_name compute_resources { instance_role = aws_iam_instance_profile.iam-role-for-aws-batch-gpu.arn instance_type = ["g4dn.xlarge"] max_vcpus = 256 subnets = local.subnet_list security_group_ids = ["${var.security_group_ids}"] type = "EC2" desired_vcpus = 0 } type = "MANAGED" service_role = aws_iam_role.aws_batch_service_role.arn depends_on = [aws_iam_role_policy_attachment.aws_batch_service_role_AWSBatchServiceRole] } # ジョブキュー resource "aws_batch_job_queue" "job_queue" { name = local.job_queue state = "ENABLED" priority = 0 compute_environments = [aws_batch_compute_environment.compute_environment.arn] } # インスタンスロール resource "aws_iam_instance_profile" "iam-role-for-aws-batch-gpu" { name = local.instance_role_profile role = aws_iam_role.instance_role.name } data "aws_iam_policy" "S3_for_instance_role" { arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" } data "aws_iam_policy" "aws_batch_for_instance_role_AmazonEC2ContainerServiceforEC2Role" { arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" } resource "aws_iam_policy" "S3_for_instance_role" { name = local.S3_for_instance_role policy = data.aws_iam_policy.S3_for_instance_role.policy } resource "aws_iam_policy" "aws_batch_for_instance_role_AmazonEC2ContainerServiceforEC2Role" { name = local.AmazonEC2ContainerServiceforEC2Role policy = data.aws_iam_policy.aws_batch_for_instance_role_AmazonEC2ContainerServiceforEC2Role.policy } resource "aws_iam_role_policy_attachment" "S3_for_instance_role" { role = aws_iam_role.instance_role.name policy_arn = aws_iam_policy.S3_for_instance_role.arn } resource "aws_iam_role_policy_attachment" "aws_batch_for_instance_role_AmazonEC2ContainerServiceforEC2Role" { role = aws_iam_role.instance_role.name policy_arn = aws_iam_policy.aws_batch_for_instance_role_AmazonEC2ContainerServiceforEC2Role.arn } data "aws_iam_policy_document" "assume_role_for_instance" { statement { effect = "Allow" principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } actions = ["sts:AssumeRole"] } } resource "aws_iam_role" "instance_role" { name = local.instance_role assume_role_policy = data.aws_iam_policy_document.assume_role_for_instance.json } # サービスロール用のIAMロール data "aws_iam_policy" "for_service_role_AWSBatchServiceRole" { arn = "arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole" } resource "aws_iam_policy" "aws_batch_service_role_AWSBatchServiceRole" { name = local.AWSBatchServiceRole policy = data.aws_iam_policy.for_service_role_AWSBatchServiceRole.policy } resource "aws_iam_role_policy_attachment" "aws_batch_service_role_AWSBatchServiceRole" { role = aws_iam_role.aws_batch_service_role.name policy_arn = aws_iam_policy.aws_batch_service_role_AWSBatchServiceRole.arn } data "aws_iam_policy_document" "assume_role_for_service_role" { statement { effect = "Allow" principals { type = "Service" identifiers = ["batch.amazonaws.com"] } actions = ["sts:AssumeRole"] } } resource "aws_iam_role" "aws_batch_service_role" { name = local.aws_batch_service_role assume_role_policy = data.aws_iam_policy_document.assume_role_for_service_role.json }
EventBridgeの設定
- aws_cloudwatch_event_rule
- event_patternという項目で、発火条件を設定する
- aws_cloudwatch_event_target
- 発火後にデータを渡すAWSリソースの情報を書く
- aws_iam_policy_document
- EventBridgeを動かすために必要なものを定義するので、"events.amazonaws.com"を記載
- aws_iam_role
- ターゲット先のAWSリソースの指定をする必要がある
resource "aws_cloudwatch_event_rule" "eventbridge_rule" { name = local.event_rule description = "S3の特定フォルダ配下にファイルアップロードしたときに、AWS Batchを起動するルールをIaC化" event_pattern = jsonencode({ "detail-type" : ["Object Created"], "source" : ["aws.s3"], "detail" : { "bucket" : { "name" : ["${local.bucket_name}"] }, "object" : { "key" : [{ "prefix" : "input-file/" }] } } }) } resource "aws_cloudwatch_event_target" "eventbridge_target" { rule = aws_cloudwatch_event_rule.eventbridge_rule.name arn = aws_batch_job_queue.job_queue.arn # ジョブキューのARN batch_target { job_definition = aws_batch_job_definition.job_definition.arn job_name = local.job_name } input_transformer { input_paths = { "input_bucket_name" : "$.detail.bucket.name", "input_s3_key" : "$.detail.object.key" } input_template = <<TEMPLATE {"Parameters": {"input_bucket_name":"<input_bucket_name>", "input_s3_key":"<input_s3_key>","output_bucket_name":"${local.bucket_name}", "output_s3_key":"output-eventbridge-test/output.csv"}} TEMPLATE } role_arn = aws_iam_role.eventbridge_service_role.arn } data "aws_iam_policy_document" "eventbridge_test" { statement { effect = "Allow" principals { type = "Service" identifiers = ["events.amazonaws.com"] } actions = ["sts:AssumeRole"] } } resource "aws_iam_role" "eventbridge_service_role" { name = local.eventbridge_service_role assume_role_policy = data.aws_iam_policy_document.eventbridge_test.json inline_policy { name = local.eventbridge_policy policy = jsonencode({ "Version" : "2012-10-17" "Statement" : [ { Effect = "Allow" Action = [ "batch:SubmitJob" ], Resource = [ "arn:aws:batch:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:job/${local.job_name}", "${aws_batch_job_definition.job_definition.arn}", "${aws_batch_job_queue.job_queue.arn}" ] } ] }) } }
動かしてみた
ここまでで設定した内容を使ってAWSリソースの作成を行います。
まず初めにterraform init
コマンドを実行してください。その後にterraform apply
コマンドを実行します。(tfファイルの作成途中であれば、terraform validate
やterraform plan
で書いた内容が問題ないか確認しますが、今回は動作チェックを行なったtfファイルですので飛ばしています。)
CLIで次の表示が出ればAWSリソース作成が完了です。
Apply complete! Resources: 18 added, 0 changed, 18 destroyed.
まずはS3を開いてください。input-file
というフォルダがあるので実際にファイルをアップロードしてみます。
アップロード完了後にAWS Batchの画面を開きます。ダッシュボードを見てみると、ジョブが動いているのを確認できます。
少し待つと、ジョブが完了している事を確認できました!(一度実行していたので成功数は2の表記になっています。)
そして冒頭にも書いたとおり、ファイルをアップロード出来ている事も確認できました。
AWSリソースの削除方法
今回作成したAWSリソースを削除したい場合は、S3バケットの中身を空にして、terraform destroy
コマンドを叩いてください。
終わりに
今回は、S3とAWS Batchの連携のためにEventBridgeを使いました。また、それらAWSリソースをTerraformを使って作成しています。
Terraformを使うと簡単にAWSリソースを作成できるので便利ですね! 前回書いた『EventBridgeの発火条件にしたS3バケットとフォルダ含むファイル名をパラメータとして受け渡ししてみた』についても、IaC化してみようかと思います。
今回はここまで。
それでは、また!